using System;
using System.Collections.Generic;
using System.Linq;
using PingPong.Generic;
using PingPong.Generic.Helpers;
using PingPong.GameEngine.Components;

namespace PingPong.GameEngine
{

	public class GameEvent
	{
		#region Members

		/// <summary>
		/// The game action.
		/// </summary>
		private Lazy<List<GameAction>> gameActions;

		/// <summary>
		/// The game condition.
		/// </summary>
		private Lazy<List<GameCondition>> gameConditions; 

		#endregion

		#region Properties

		/// <summary>
		/// Gets the game action list.
		/// </summary>
		/// <value>The game action list.</value>
		private List<GameAction> GameActionList
		{
			get
			{
				return this.gameActions.Value;
			}
		}

		/// <summary>
		/// Gets the game action.
		/// </summary>
		/// <value>The game action.</value>
		public IEnumerable<GameAction> GameActions
		{
			get
			{
				return this.GameActionList;
			}
		}

		/// <summary>
		/// Gets the game condition list.
		/// </summary>
		/// <value>The game condition list.</value>
		private List<GameCondition> GameConditionList
		{
			get
			{
				return this.gameConditions.Value;
			}
		}

		/// <summary>
		/// Gets the game conditions.
		/// </summary>
		/// <value>The game conditions.</value>
		public IEnumerable<GameCondition> GameConditions
		{
			get
			{
				return this.GameConditionList;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="PingPong.GameEngine.GameEvent"/> class.
		/// </summary>
		public GameEvent()
		{
			this.gameActions = new Lazy<List<GameAction>>(() => new List<GameAction>());
			this.gameConditions = new Lazy<List<GameCondition>>(() => new List<GameCondition>());
		}

		#endregion

		#region Methods

		#region Events

		/// <summary>
		/// Fires the OnPreEvaluation event.
		/// </summary>
		/// <returns>The on pre evaluation.</returns>
		private GameEvent FireOnPreEvaluation()
		{
			EventHelper.SafeFireEvent(OnPreEvaluation,this,new EventArgs());
			return this;
		}

		/// <summary>
		/// Fires the OnPostEvaluation event.
		/// </summary>
		/// <returns>The on post evaluation.</returns>
		/// <param name="contionEvaluation">If set to <c>true</c> contion evaluation.</param>
		private GameEvent FireOnPostEvaluation(bool conditionEvaluation)
		{
			EventHelper.SafeFireEvent(OnPostEvaluation,this,new DataEventArgs<bool>(conditionEvaluation));
			return this;
		}

		#endregion

		/// <summary>
		/// Sets the game action.
		/// </summary>
		/// <returns>The game action.</returns>
		/// <param name="gameAction">Game action.</param>
		public GameEvent AddGameAction(GameAction gameAction)
		{
			this.GameActionList.Add(gameAction);
			return this;
		}

		/// <summary>
		/// Adds the game condition.
		/// </summary>
		/// <returns>The game condition.</returns>
		/// <param name="gameCondition">Game condition.</param>
		public GameEvent AddGameCondition(GameCondition gameCondition)
		{
			this.GameConditionList.Add(gameCondition);
			return this;
		}

		/// <summary>
		/// Evaluate this instance.
		/// </summary>
		public bool Evaluate()
		{
			FireOnPreEvaluation();

			if(!this.GameConditionList.Any())
			{
				throw new ArgumentOutOfRangeException("GameConditionList");
			}

			bool conditionEvaluation = false;
			var firstCondition = this.GameConditionList.First();
			var conditionAndGroups = new List<IEnumerable<GameCondition>>();
			var group = new List<GameCondition>();

			//Always evaluate the first condtion. Its type should be of None.
			conditionEvaluation = firstCondition.Evaluate();

			if(conditionEvaluation)
			{
				group.Add(firstCondition);

				foreach(var condition in this.GameConditionList)
				{
					if(condition.ExpressionBooleanType == ExpressionBooleanType.And)
					{
						group.Add(condition);
					}
					else if(condition.ExpressionBooleanType == ExpressionBooleanType.Or)
					{
						conditionAndGroups.Add(group);
						group.Clear();
						group.Add(condition);
					}
					else
					{
						throw new ArgumentException("condition.ExpressionBooleanType");
					}
				}

				conditionAndGroups.Add(group);

				conditionEvaluation = conditionAndGroups.Select(g => g.All(g2 => g2.Evaluate())).Aggregate((working, current) => working || current);
			}

			if (conditionEvaluation)
			{
				foreach (var gameAction in this.GameActionList)
				{
					gameAction.Execute();
				}
			}

			FireOnPostEvaluation(conditionEvaluation);

			return conditionEvaluation;
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs before evaluation of the condition.
		/// </summary>
		public event EventHandler<EventArgs> OnPreEvaluation;
		/// <summary>
		/// Occurs after evaluation of the condition.
		/// </summary>
		public event EventHandler<DataEventArgs<bool>> OnPostEvaluation;

		#endregion
	}
}

